﻿using System;
using System.Collections.Generic;
using SimpleServiceBus.Bus.MessageManagement;
using SimpleServiceBus.Endpoint;

namespace SimpleServiceBus.Bus.Pipeline
{
    public enum PipelineStatus
    {
        Uninitialized = 0,
        Initializing,
        Initialized,
        Processing,
        Complete
    }

    public class MessagePipeline : IMessagePipeline
    {
        private PipelineStatus _status;
        protected PipelineEvents _events;

        protected MessagePipeline(PipelineEvents events):this()
        {
            if (events != null)
                _events = events;
        }
        
        public MessagePipeline()
        {
            _events = new PipelineEvents();
            PipelineComponents = new List<IMessagePipelineComponent>();
        }

        public IMessagingEndpoint Endpoint { get; private set; }
        public PipelineDirection PipelineDirection { get; private set; }
        public IPipelineEvents PipelineEvents
        {
            get { return _events; }
        }

        public IList<IMessagePipelineComponent> PipelineComponents { get; protected set; }


        public PipelineStatus Status
        {
            get { return _status; }
            private set
            {
                if (_status != value)
                {
                    PipelineStatus prev = _status;

                    _status = value;

                    var message = CurrentContext == null ? null : CurrentContext.MessageEnvelope;

                    _events.OnPipelineStatusChanged(new MessagePipelineStatusChangedEventArgs
                                                {Pipeline = this, PreviousStatus = prev, CurrentStatus = value,Message = message});
                }
            }
        }

        public int CurrentComponentIndex { get; private set; }

        #region IMessagePipeline Members
       

        public IMessageContext CurrentContext { get; private set; }
        public IMessagePipelineComponent CurrentComponent { get; private set; }


        public virtual void Initialize(MessageEnvelope messageEnvelope,IMessagingEndpoint endpoint,PipelineDirection direction,params IMessagePipelineComponent[] pipelineComponents)
        {
            SetCurrentPipeline(this, direction);

            PipelineDirection = direction;
            Endpoint = endpoint;
            
            if (pipelineComponents != null)
                ((List<IMessagePipelineComponent>)PipelineComponents).AddRange(pipelineComponents);

           
            if (Status > PipelineStatus.Uninitialized)
                throw new InvalidOperationException(
                    "This MessageEnvelope Processing Pipeline has already been initialized. Initialized can only be called once.");

            Status = PipelineStatus.Initializing;

            CurrentContext = CreateMessageContext(messageEnvelope);
            InitializePipelineComponents();

            Status = PipelineStatus.Initialized;
            _events.OnPipelineInitialized(new MessagePipelineEventArgs{Pipeline = this,Message = CurrentContext.MessageEnvelope});
        }

        public void ProcessMessage()
        {
            try
            {
                if (Status != PipelineStatus.Initialized)
                    throw new InvalidOperationException("The current status of this MessageEnvelope Pipeline is " + Status +
                                                        ". Process() can only be called when the current status is Initialized.");
                Status = PipelineStatus.Processing;

                _events.OnPipelineStarted(new MessagePipelineEventArgs { Pipeline = this, Message = CurrentContext.MessageEnvelope});

                if (PipelineComponents != null)
                {
                    for (CurrentComponentIndex = 0; CurrentComponentIndex < PipelineComponents.Count; CurrentComponentIndex++)
                    {
                        IMessagePipelineComponent component = PipelineComponents[CurrentComponentIndex];

                        CurrentComponent = component;

                        if (component.Enabled == false ||
                            component.EnabledFor(CurrentContext.MessageEnvelope) == false)
                            continue;


                        _events.OnProcessingComponentStarted(new MessagePipelineEventArgs { Pipeline = this, Message = CurrentContext.MessageEnvelope });

                        component.ProcessMessage(CurrentContext.MessageEnvelope);

                        _events.OnProcessingComponentCompleted(new MessagePipelineEventArgs { Pipeline = this, Message = CurrentContext.MessageEnvelope });

                        if (CurrentContext.AbortMessageProcessing)
                            break;
                    }
                }

                Status = PipelineStatus.Complete;
                CurrentComponent = null;

                _events.OnPipelineCompleted(new MessagePipelineEventArgs { Pipeline = this, Message = CurrentContext.MessageEnvelope });

            }
            catch (Exception ex)
            {
                ex =
                    new MessageProcessingException(
                        "An error occurred processing a message in pipeline component " + GetType().Name, ex,
                        CurrentContext.MessageEnvelope);
                if (!_events.OnPipelineError(new MessagePipelineErrorEventArgs {Pipeline = this, Error = ex, Message = CurrentContext.MessageEnvelope}))
                    throw;
            }
            finally
            {
                ClearCurrentPipeline(PipelineDirection);
            }
        }


        public virtual void Dispose()
        {
            CurrentReceivePipeline = null;
            CurrentContext = null;
            foreach (IMessagePipelineComponent component in PipelineComponents)
            {
                component.Dispose();
            }
        }

        #endregion

        protected virtual void InitializePipelineComponents()
        {
            foreach (IMessagePipelineComponent pipelineComponent in PipelineComponents)
            {
                pipelineComponent.Pipeline = this;
            }
        }

        protected virtual IMessageContext CreateMessageContext(MessageEnvelope messageEnvelope)
        {
            return new MessageContext(messageEnvelope, this);
        }

      

        #region Thead Status Singleton

        [ThreadStatic] private static IMessagePipeline _currentReceivePipeline;

        public static IMessagePipeline CurrentReceivePipeline
        {
            get { return _currentReceivePipeline; }
            protected set { _currentReceivePipeline = value; }
        }

        [ThreadStatic]
        private static IMessagePipeline _currentSendPipeline;

        public static IMessagePipeline CurrentSendPipeline
        {
            get { return _currentSendPipeline; }
            protected set { _currentSendPipeline = value; }
        }

        public static void SetCurrentPipeline(IMessagePipeline pipeline,PipelineDirection direction)
        {
            if (direction == PipelineDirection.Receive)
            {
                if (CurrentReceivePipeline != null)
                    throw new InvalidOperationException(
                        "Only oneReceivePipeline is allowed per thread. A MessagePipline has already been asigned to this thread.");

                CurrentReceivePipeline = pipeline;
            }
            else
            {
                if (CurrentSendPipeline != null)
                    throw new InvalidOperationException(
                        "Only one SendPipeline is allowed per thread. A MessagePipline has already been asigned to this thread.");
                
                CurrentSendPipeline = pipeline;
            }
        }

        public static void ClearCurrentPipeline(PipelineDirection direction)
        {
            if (direction == PipelineDirection.Receive)
                CurrentReceivePipeline = null;
            else
                CurrentSendPipeline = null;
        }

        #endregion
    }
}